
// ===============================================================================================================
// -*- C++ -*-
//
// Camera.hpp - A first person style 3D camera.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#ifndef __CAMERA_HPP__
#define __CAMERA_HPP__

// Local includes
#include <Math.hpp>
#include <Vector.hpp>

///
/// Camera -- A "Quake like" first person 3D camera.
/// \note System dependant routines such as keybord and mouse input are done outside
/// the camera. Use the provided methods to set its data.
///
class Camera {

public:

	// Public Interface:

	Camera(void);

	Camera(const Vec3 & rightVec, const Vec3 & upVec, const Vec3 & forwardVec, const Vec3 & eyeVec);

	/// Resets to a "starting" position.
	void Reset(const Vec3 & rightVec, const Vec3 & upVec, const Vec3 & forwardVec, const Vec3 & eyeVec);

	/// Pitches camera by "angle" (in radians).
	void Pitch(float angle);

	/// Rotates around world Y-axis by the given angle (in radians).
	void Rotate(float angle);

	///
	/// Valid directions to move the camera in:
	///
	enum MoveDir {

		FORWARD, ///< Move forward relative to the camera's space
		BACK,    ///< Move backward relative to the camera's space
		LEFT,    ///< Move left relative to the camera's space
		RIGHT    ///< Move right relative to the camera's space
	};

	/// Moves the camera by the given direction, using the provided movement amount.
	/// The last three parameters indicate in which axis to move. If it is equal to 1, move in that axis, if it is zero don't move.
	void Move(MoveDir dir, float amount, float x, float y, float z);

	// Get / Set:

	inline void SetRight(const Vec3 & rightVec) { right = rightVec; }
	inline void SetUp(const Vec3 & upVec) { up = upVec; }
	inline void SetForward(const Vec3 & forwardVec) { forward = forwardVec; }
	inline void SetEye(const Vec3 & eyeVec) { eye = eyeVec; }

	inline Vec3 GetRight(void) const { return (right); }
	inline Vec3 GetUp(void) const { return (up); }
	inline Vec3 GetForward(void) const { return (forward); }
	inline Vec3 GetEye(void) const { return (eye); }

	/// This function returns what the camera is looking at. Our eye is ALWAYS the origin
	/// of camera's coordinate system and we are ALWAYS looking straight down the "forward" axis
	/// so to calculate the target it's just a matter of adding the eye plus the forward.
	inline Vec3 GetTarget(void) const
	{
		return (Vec3(eye.x + forward.x, eye.y + forward.y, eye.z + forward.z));
	}

private:

	/*	Initial Camera Axes:

		(up)
		+Y   +Z (forward)
		|   /
		|  /
		| /
		+ ------ +X (right)
	*/

	/// This allows us to rotate "vec" around an arbitrary "axis" by a the "angle" in radians.
	static void RotateAroundAxis(Vec3 & result, const Vec3 & vec, const Vec3 & axis, float angle);

private:

	// Private Data:

	Vec3 right;   ///< The normalized axis that points to the "right"
	Vec3 up;      ///< The normalized axis that points "up"
	Vec3 forward; ///< The normalized axis that points "forward"
	Vec3 eye;     ///< The position of the camera (i.e. the camera's eye and the origin of the camera's coordinate system)
};

#endif // __CAMERA_HPP__